﻿///
/// NetworkSetup.fs
/// Defines the most important elements for setting a network.
///

namespace PathFinder

open System
open System.Collections
open System.Collections.Generic

open PathFinder.Network

#light

///
/// Contains the most important elements to setup a network.
///
module public NetworkSetup =
    (* Constants *)
    ///
    /// Defines the standard value assigned to connections of non-connected nodes.
    ///
    [<Literal>]
    let public ConnectionInfiniteWeight = -1.0
    ///
    /// Defines the standard fastest connection.
    ///
    [<Literal>]
    let public ConnectionInstantWeight = 0.0
    (* Types *)
    ///
    /// A parallel type used to build the final network. It represents a node.
    ///
    type public SetupNode = {
        Node: NodeIdentifier;
        Coordinates: NodeCoordinate
        }
    ///
    /// A parallel type used to build the final network. It represents a connection.
    ///
    type public SetupConnection = {
        NodeFrom: NodeIdentifier;
        NodeTo: NodeIdentifier;
        Cost: ConnectionWeight
        }
    ///
    /// Represents a key for the adjacency matrix
    ///
    type public NetworkAdjacencyMatrixKey = NodeIdentifier * NodeIdentifier
    ///
    /// Represents the holder for the AdjacencyMatrix.
    /// Given an entry <NodeFrom, NodeTo>, we get the adjacency matrix value (the connection cost).
    ///
    type public NetworkAdjacencyMatrix = Dictionary<NetworkAdjacencyMatrixKey, ConnectionWeight>
    (* Functions *)
    ///
    /// Adds an element in the specified adjacency matrix.
    ///
    let public AddAdjacency (adjMatrix: NetworkAdjacencyMatrix, adjKey: NetworkAdjacencyMatrixKey, adjWeight: ConnectionWeight) =
        adjMatrix.[adjKey] <- abs (adjWeight)
    ///
    /// Tells whether the adjacency matrix has been filled with all nodes.
    ///
    let public IsAdjacencyFilled (adjMatrix: NetworkAdjacencyMatrix, nodesNumber: int) =
        adjMatrix.Count = nodesNumber
    ///
    /// Normalizes the newtork adjacency matrixes inserting the 0 in the main diagonal and -1 in all non filled places.
    /// Always call this before using the matrix. It returns the Normalized adjacency matrix.
    ///
    let public NormalizeAdjacencyMatrix (adjMatrix: NetworkAdjacencyMatrix) =
        let mutable normAdj = adjMatrix
        (* Getting all nodes *)
        let mutable nodes = new Set<NodeIdentifier> ([])
        for i in adjMatrix do
            nodes <- (nodes.Add (fst i.Key))
            nodes <- (nodes.Add (snd i.Key))
        (* Here I have all nodes, process everyone of them and create an entry A -> A assigning INSTANT as weight *)
        (* Inserting INSTANT in the main diagonal *)
        for i in nodes do
            normAdj.[(i, i)] <- ConnectionInstantWeight
        (* Inserting INFINITE in the remaining fills *)
        for i in nodes do
            for j in nodes do
                try
                    normAdj.[(i, j)] |> (fun x -> ()) (* Dummy op *)
                with
                    | :? KeyNotFoundException -> 
                        normAdj.[(i, j)] <- ConnectionInfiniteWeight
        normAdj
    ///
    /// Prints the adjacency matrix.
    ///
    let public PrintAdjacencyMatrix (adjMatrix: NetworkAdjacencyMatrix) =
        let linefeed = "\n"
        let mutable output = ""
        (* Getting all nodes *)
        let mutable nodes = new Set<NodeIdentifier> ([])
        for i in adjMatrix do
            nodes <- (nodes.Add (fst i.Key))
            nodes <- (nodes.Add (snd i.Key))
        (* Getting max length to correctly print a fine matrix *)
        let mutable lengths = new Set<int> ([])
        for i in adjMatrix do
            let str = ((fst i.Key).ToString ())
            lengths <- (lengths.Add (str.Length))
        let length = lengths.MaximumElement
        (* Creating separator *)
        let sep = new String (' ', length)
        let moresep = "   "
        (* Printing *)
        output <- (output + sep + moresep + "|| ")
        for k in nodes do
            output <- (output + k + " || ")
        output <- (output + linefeed)
        for i in nodes do
            output <- output + i + moresep + "|| "
            for j in nodes do
                output <- (output + " ")
                try
                    let el = adjMatrix.[(i, j)]
                    output <- (output + (el.ToString ()) + sep + "  ")
                with
                    | :? KeyNotFoundException as ex -> 
                        output <- (output + "?" + sep + "  ")
                    | :? ArgumentNullException as ex -> 
                        output <- (output + "?" + sep + "  ")
            output <- output + linefeed
        output
    
